using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace cs2335.lab5
{
    public class TextBox: IComponent,ITextBox
    {
        protected Rectangle textBoxRectangle;
        //rectangle to give the control the look of an interactive component
        protected Rectangle outerBoxRectangle = new Rectangle(0, 0, 104, 24);
        protected StringFormat txtBoxFormat = new StringFormat(StringFormatFlags.LineLimit);
        protected SolidBrush textBoxBrush = new SolidBrush(Color.White);
        protected SolidBrush outerBoxBrush = new SolidBrush(Color.DarkKhaki);
        protected Font font = new Font("SansSerif", 10);
        protected CCTForm owner;
        protected string leftSideTxt = "";
        protected string rightSideTxt = "|";
        protected bool hasFocus = false;
        protected bool hidden = false;
        protected string name;
        //size of text string
        protected float textsize = 0.0F;
        //size of the maximum number of allowed characters
        protected double length;
        //maximum number of characters allowed;
        protected int maxNumChars;
        //array of all of the characters bounding rectangles
        protected RectangleF[] characterPositions;

        #region Properties
        /// <summary>
        /// Tells whether or not a component is hidden. If it is hidden,
        /// it should not be receiving actions.
        /// </summary>
        public bool Hidden
        {
            get { return hidden; }
        }

        /// <summary>
        /// Stores the width and height of the component
        /// </summary>
        public Size Size
        {
            
            get { return textBoxRectangle.Size; }
            set {
                textBoxRectangle.Size = value;
                outerBoxRectangle.Size = new Size(textBoxRectangle.Size.Width + (int)Pens.White.Width,
                    textBoxRectangle.Size.Height + (int)Pens.White.Width);
                Invalidate(); }
        }

        /// <summary>
        /// allows the location of the outerborder of the textbox to be set, and
        /// uses this location to set the location of the input area
        /// </summary>
        public Point Location
        {
            get { return outerBoxRectangle.Location; }
            set { outerBoxRectangle.Location= value;
                textBoxRectangle.Location = new Point(outerBoxRectangle.X + (int)Pens.White.Width,
                    outerBoxRectangle.Y + (int)Pens.White.Width); 
                Invalidate(); }
        }

        public Rectangle Bounds
        {
            get { return textBoxRectangle; }
            set 
            { 
                outerBoxRectangle = value;
                textBoxRectangle = new Rectangle(outerBoxRectangle.X + (int)Pens.White.Width,
                    outerBoxRectangle.Y + (int)Pens.White.Width, 
                    outerBoxRectangle.Width - 2 * (int)Pens.White.Width,
                    outerBoxRectangle.Height - 2 * (int)Pens.White.Width); 
                Invalidate(); 
            }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public System.Drawing.Font Font
        {
            get { return font; }
            set { font = value; Invalidate(); }
        }
        public CCTForm Owner
        {
            get { return owner; }
            set { owner = value; }
        }

        public Color BackColor
        {
            get { return textBoxBrush.Color; }
            set { textBoxBrush.Color = value; Invalidate(); }
        }
        /// <summary>
        /// sets the size that correlates with the number of characters allowed to be entered
        /// into the textbox
        /// </summary>
        public int MaxLength
        {
            get { return maxNumChars; }
            set
            {length = value*font.Size; maxNumChars = value;}
        }
        /// <summary>
        /// returns the text as a concatenated string of the text on the
        /// right and left side of the cursor minus the cursor. Not sure if this is how I will actually
        /// be implementing this...might just insert cursor where into appropriate location
        /// <para >text string</para>
        /// </summary>
        public string Text
        {
            get { return leftSideTxt + rightSideTxt.TrimStart('|'); }
            ///set the text on the left side of the cursor to the data and set the right
            /// side to be the cursor since when text is entered the cursor should be at the end/right side
            set { leftSideTxt = value; rightSideTxt = "|";
                Invalidate(); }
        }
        public Color BorderColor
        {
            get { return outerBoxBrush.Color; }
            set { outerBoxBrush.Color = value; }
        }
        #endregion
        #region Events
        public event EventHandler Click;
        public event EventHandler GotFocus;
        public event EventHandler KeyPress;
        public event EventHandler KeyDown;
        public event EventHandler LostFocus;
        public event EventHandler Paint;
        #endregion
        #region Constructors
        /// <summary>
        /// set the text alignment to left of the input rectangle initializing the text
        /// to be blank by, setting left side text of cursor to "". Right side text
        /// always contains the cursor followed by text.
        /// sets the number of characters allowed to be entered to be the approcimate number
        /// of characters that will fit into the visible input space
        /// </summary>
        public TextBox()
        {
            textBoxRectangle = new Rectangle((int)Pens.White.Width, (int)Pens.White.Width,
                outerBoxRectangle.Width - 2* (int)Pens.White.Width, outerBoxRectangle.Height - 2* (int)Pens.White.Width);
            txtBoxFormat.LineAlignment = StringAlignment.Near;
            txtBoxFormat.Alignment = StringAlignment.Near;
            leftSideTxt = "";
            rightSideTxt = "|";
            length = textBoxRectangle.Width;
            maxNumChars = textBoxRectangle.Width / Convert.ToInt32(font.Size);
        }
        #endregion
        public void Invalidate()
        {
            if (owner != null)
            {
               owner.Invalidate();
            }
        }
        public void Hide()
        {
            hidden = true;
            Invalidate();
        }
        public void Show()
        {
            hidden = false;
            Invalidate();
        }
        public void OnPaint(PaintEventArgs e)
        {
            if (!hidden)
            {   
                Graphics g = e.Graphics;
                //draw text box
                g.FillRectangle(textBoxBrush, textBoxRectangle);
                //size of the text string
                textsize = g.MeasureString(Text, font).Width;

                ///Following code used to get the bounding graphic rectangles
                ///of each character in the text string
                CharacterRange[] ranges = new CharacterRange[Text.Length];
                for (int i = 0; i < Text.Length; i++)
                {
                    ranges[i] = new CharacterRange(i, 1);
                }
                StringFormat charFormat = new StringFormat(StringFormatFlags.NoClip);
                charFormat.SetMeasurableCharacterRanges(ranges);
                Region[] charRegions = g.MeasureCharacterRanges(Text, font, (RectangleF)(new RectangleF(textBoxRectangle.Location, new SizeF(textsize,font.GetHeight()))), charFormat);
                int n = 0;
                characterPositions = new RectangleF[charRegions.Length];
                foreach (Region x in (charRegions))
                {
                    characterPositions[n] = x.GetBounds(g);
                    n++;
                }
                
                ///********************************Testing Purposes*******************
                ///g.DrawString(hasFocus.ToString(),font,Brushes.Black,100,200);
                ///g.DrawString("right = "+ rightSideTxt + "   " + leftSideTxt , font, Brushes.Black, 100, 230);
                 ///g.DrawString(length.ToString(), font, Brushes.Black, 100, 250);
                ///*******************************************************************
                //Redraw text with cursor to show that the TextBox has focus 
                int margin = 2;
                if (hasFocus)
                {
                    DrawFocusBorder(g, outerBoxRectangle);
                    //g.DrawString((leftSideTxt + rightSideTxt), font, Brushes.Black, (RectangleF)textBoxRectangle, txtBoxFormat);
                    g.DrawString((leftSideTxt + rightSideTxt), font, Brushes.Black, new PointF(textBoxRectangle.X + margin, textBoxRectangle.Y + margin));
                }
                else
                {
                    Draw3DBorder(g, outerBoxRectangle);
                    //draw text without cursor
                    g.DrawString(Text, font, Brushes.Black, new PointF(textBoxRectangle.X + margin, textBoxRectangle.Y + margin));
                    //g.DrawString(Text, font, Brushes.Black, (RectangleF)textBoxRectangle, txtBoxFormat);
                }
                if (Paint != null)
                {
                    Paint(this, e);
                }
            }
        }
        public void OnGotFocus(EventArgs e)
        {
            hasFocus = true;          
            if (GotFocus != null)
            {
                GotFocus(this, e);
            }
            Invalidate();           
        }
        public void OnLostFocus(EventArgs e)
        {
            hasFocus = false;
            if (LostFocus != null)
            {
                LostFocus(this, e);
            }
            Invalidate();
        }
        public void OnMouseClick(System.Windows.Forms.MouseEventArgs e)
        {
            
            ///Relocates the cursor when user clicks in the text box
            /// compares the location of the mouse click with the right boundary of the
            /// each character till it finds the character at the approximate location of the
            /// click, inserts cursor here
            int index = 0;
            int mouseLocation = 0;
            string temp="";
            int i = 0;
            mouseLocation = e.X-textBoxRectangle.Left;            
            ///*********************************Testing Purposes*************************
            ///Help.ShowPopup(owner, mouseLocation.ToString()+" " + e.X.ToString(), new Point(100, 100));
            ///***************************************************************************
            if (Text.Length > 0)
            {
                while (e.X > characterPositions[i].Right && i < Text.Length-1)
                {
                    i++;
                }
                index = i;
                if(mouseLocation <= textsize)
                {
                    if (mouseLocation < .5)
                    {
                        rightSideTxt = rightSideTxt.Trim('|');
                        rightSideTxt = "|" + leftSideTxt + rightSideTxt;
                        leftSideTxt = "";
                    }
                    else
                    {
                        temp = Text;
                        rightSideTxt = temp.Substring(index);
                        rightSideTxt = rightSideTxt.Insert(0, "|");
                        leftSideTxt = temp.Substring(0,index);
                    }
                }
                else if (mouseLocation >=textsize )
                {
                    leftSideTxt = leftSideTxt + rightSideTxt.TrimStart('|');
                    rightSideTxt = "|";
                }
            }
            if (Click != null)
            {
                Click(this, e);
            }
            Invalidate();
        }

        public void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
        {
            /* ignore */
        }

        public void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
        {
            /* ignore */
        }

        public void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
        {
            /* ignore */
        }
        /// <summary>
        /// Detects none char keyboard actions and updates text
        /// </summary>
        /// <param name="e"></param>
        public void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Right)
            {
                if (rightSideTxt.Length > 1)
                {
                    leftSideTxt += rightSideTxt.Substring(1, 1);
                    rightSideTxt = rightSideTxt.Substring(2);
                    rightSideTxt = rightSideTxt.Insert(0, "|");
                }
            }
            else if (e.KeyCode == Keys.Left)
            {
                if (leftSideTxt.Length > 0)
                {
                    rightSideTxt = rightSideTxt.TrimStart('|');
                    rightSideTxt = leftSideTxt.Substring(leftSideTxt.Length - 1) + rightSideTxt;
                    rightSideTxt = rightSideTxt.Insert(0, "|");
                    leftSideTxt = leftSideTxt.Remove(leftSideTxt.Length - 1);
                }
            }
            else if (e.KeyCode == Keys.Delete && rightSideTxt.Length > 1)
            {
                rightSideTxt = rightSideTxt.Remove(1,1);
            }
            if (KeyDown != null)
            {
                KeyDown(this, e);
            }
            Invalidate();
        }
        public void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
        {
            //sets the location of the cursor and adds text user types, enforces the textbox limit
            if (!hidden)
            {
                if (hasFocus)
                {
                    if (Convert.ToInt32(textsize) < length)
                    {

                        if (e.KeyChar == '\b' && Text.Length > 0)
                        {
                            leftSideTxt = leftSideTxt.Substring(0, leftSideTxt.Length - 1);
                        }
                        else if (e.KeyChar.CompareTo(' ') >= 0 && e.KeyChar.CompareTo('~') <= 0)
                        {
                            leftSideTxt = leftSideTxt.Insert(leftSideTxt.Length, e.KeyChar.ToString());
                        }
                    }
                    else
                    {
                        maxNumChars = Text.Length;
                        if (e.KeyChar == '\b' && Text.Length > 0)
                        {
                            leftSideTxt = leftSideTxt.Substring(0, leftSideTxt.Length - 1);
                        }
                    }
                } 
            }
            if (KeyPress != null)
            {
                KeyPress(this, e);
            }
            Invalidate();
        }

        private void Draw3DBorder(Graphics g, Rectangle r)
        {
            int PenWidth = (int)Pens.White.Width;

            g.DrawLine(Pens.PaleGoldenrod, new Point(r.Left, r.Top),
                new Point(r.Right - PenWidth, r.Top));
            g.DrawLine(Pens.PaleGoldenrod, new Point(r.Left, r.Top),
                new Point(r.Left, r.Bottom));
            g.DrawLine(Pens.DarkGoldenrod, new Point(r.Left, r.Bottom),
                new Point(r.Right - PenWidth, r.Bottom));
            g.DrawLine(Pens.DarkGoldenrod, new Point(r.Right - PenWidth, r.Bottom),
                new Point(r.Right - PenWidth, r.Top));
        }
        private void DrawFocusBorder(Graphics g, Rectangle r)
        {
            int PenWidth = (int)Pens.White.Width;

            g.DrawLine(Pens.DarkGoldenrod, new Point(r.Left, r.Top),
                new Point(r.Right - PenWidth, r.Top));
            g.DrawLine(Pens.DarkGoldenrod, new Point(r.Left, r.Top),
                new Point(r.Left, r.Bottom));
            g.DrawLine(Pens.PaleGoldenrod, new Point(r.Left, r.Bottom),
                new Point(r.Right - PenWidth, r.Bottom));
            g.DrawLine(Pens.PaleGoldenrod, new Point(r.Right - PenWidth, r.Bottom),
                new Point(r.Right - PenWidth, r.Top));
        }
    }
}
